import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.IOException;

import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;

/**
 * Class holding data relevant to a pattern (timestamps and values). <br />
 * @author Aurelian Radoaca
 * @version 1.0
 */
class Patt
{
  /** Pattern ID (initialized in the constructor) */
  private int id;
  /**
   * Pattern length (default specified in the program)
   * or received through a command line argument,
   * initialized in the constructor
   */
  private int patternLength;
  /** Start timestamps for current pattern */
  private List<String> startTimestamps = null;
  /** End timestamps for current pattern */
  private List<String> endTimestamps = null;
  /** List of {@link #patternLength} concatenated values */
  private String values = null;
  /**
   * Non-default constructor (the single constructor).
   * The <i>pattern ID</i> and <i>pattern length</i> private data are initialized here.
   * The <i>pattern ID</i> can be modified later.
   */
  public Patt(int id, int pattlen)
  {
    this.id = id;
    patternLength = pattlen;
  }
  /**
   * Getter method
   * @return Pattern ID
   */
  public int getID()
  {
    return id;
  }
  /**
   * Setter method
   * @param id New {@link #id pattern id}
   */
  public void setID(int id)
  {
    this.id = id;
  }
  /**
   * Getter method
   * @return Pattern length
   */
  public int getPatternLength()
  {
    return patternLength;
  }
  /**
   * Getter method
   * @return List of {@link #startTimestamps}
   */
  public List<String> getStartTimestamps()
  {
    return startTimestamps;
  }
  /**
   * Setter method
   * @param times List of {@link #startTimestamps}
   */
  public void setStartTimestamps(List<String> times)
  {
    startTimestamps = new ArrayList<String>(times);
  }
  /**
   * Adds timestamp to {@link #startTimestamps}
   * @param time Timestamp to be added to {@link #startTimestamps}
   */
  public void addStartTimestamp(String time)
  {
    if (startTimestamps==null)
      startTimestamps = new ArrayList<String>();
    startTimestamps.add(time);
  }
  /**
   * Getter method
   * @return List of {@link #endTimestamps}
   */
  public List<String> getEndTimestamps()
  {
    return endTimestamps;
  }
  /**
   * Setter method
   * @param times List of {@link #endTimestamps}
   */
  public void setEndTimestamps(List<String> times)
  {
    endTimestamps = new ArrayList<String>(times);
  }
  /**
   * Adds timestamp to {@link #endTimestamps}
   * @param time Timestamp to be added to {@link #endTimestamps}
   */
  public void addEndTimestamp(String time)
  {
    if (endTimestamps==null)
      endTimestamps = new ArrayList<String>();
    endTimestamps.add(time);
  }
  /**
   * Getter method
   * @return List of {@link #values}
   */
  public String getValues()
  {
    return values;
  }
  /**
   * Setter method
   * @param values List of {@link #patternLength} values
   */
  public void setValues(String values)
  {
    this.values = values;
  }
  /**
   * Display data for the current pattern.
   * @return Pattern ID and Values
   */
  @Override public String toString() // override corresponding method in java.lang.Object
  {
    return "Pattern ID: "+id+"\tValues: "+values+'\n'+
    "Start timestamps: "+startTimestamps+'\n'+
    "End timestamps:   "+endTimestamps+'\n';
  }
}
/**
 * Class representing the frequency of a pattern. <br />
 * The frequency is computed from a beginning timestamps to an end timestamp. <br />
 * @author Aurelian Radoaca
 * @version 1.0
 */
class Freq
{
  /** Pattern ID (initialized in the constructor) */
  private int pattID;
  /** Frequency (initialized with 0)*/
  private int freq = 0;
  /** Start time */
  private String startTime = null;
  /** End time */
  private String endTime = null;
  /**
   * Non-default constructor (the single constructor).
   * The <i>pattern ID</i> private data is initialized here,
   * but can be modified later.
   */
  public Freq(int id)
  {
    pattID = id;
  }
  /**
   * Getter method
   * @return Pattern ID
   */
  public int getPattID()
  {
    return pattID;
  }
  /**
   * Setter method
   * @param id New {@link #pattID pattern id}
   */
  public void setPattID(int id)
  {
    pattID = id;
  }
  /**
   * Getter method
   * @return Frequency of the pattern identified by {@link #pattID}
   */
  public int getFreq()
  {
    return freq;
  }
  /**
   * Setter method
   * @param frequency Current pattern {@link #freq frequency}
   */
  public void setFreq(int frequency)
  {
    freq = frequency;
  }
  /** Increment frequency */
  public void incrementFreq()
  {
    freq++;
  }
  /**
   * Getter method
   * @return Start time
   */
  public String getStartTime()
  {
    return startTime;
  }
  /**
   * Setter method
   * @param time Timestamp representing the {@link #startTime start time}
   */
  public void setStartTime(String time)
  {
    startTime = time;
  }
  /**
   * Getter method
   * @return End time
   */
  public String getEndTime()
  {
    return endTime;
  }
  /**
   * Setter method
   * @param time Timestamp representing the {@link #endTime end time}
   */
  public void setEndTime(String time)
  {
    endTime = time;
  }
  /**
   * Display frequency data for the current pattern.
   * @return Pattern ID, Start time, End time, and Frequency (as absolute value)
   */
  @Override public String toString() // override corresponding method in java.lang.Object
  {
    return "Pattern ID: "+pattID+"\tFrequency: "+freq+'\n'+
    "Start time:\t"+startTime+'\n'+
    "End time:\t"+endTime+'\n';
  }
}
/**
 * Class performing pattern recognition (main class). <br />
 * Takes as input a file containing timestamps and values <br />
 * and outputs 2 files with patters and their timestamps, <br />
 * respectively pattern frequencies and their time intervals.
 * @author Aurelian Radoaca
 * @version 1.0
 */
public class Patterns
{
  /**
   * @param args Specify options as command line arguments: <br />
   * <table>
   * <tr><td> <b>inputFile</b> Mandatory argument </td></tr>
   * <tr><td> <b>patternLength</b> Optional argument, by default 5 </td></tr>
   * </table>
   * <u>Note:</u> There can be 1 or 2 command line arguments: <br />
   * The input file (mandatory) <br />
   * The input file and pattern length <br />
   */
  public static void main(String[] args) throws IOException
  {
    if ((args.length!=1) && (args.length!=2))
    {
      System.out.println("Usage: java Patterns inputFile [patternLength]");
      System.exit(0);
    }
    int patternLength = 5;
    if (args.length==2)
      patternLength = Integer.parseInt(args[1]);
    if (patternLength<2)
    {
      System.out.println("Pattern length must be at least 2");
      System.exit(0);
    }
    File inputFile = new File(args[0]);
    FileReader fr = new FileReader(inputFile);
    BufferedReader br = new BufferedReader(fr);
    String line;
    Map<Integer,Patt> pattMap = new TreeMap<Integer,Patt>(); // patterns list
    Map<Integer,Freq> freqMap = new TreeMap<Integer,Freq>(); // frequencies list
    Collection<Patt> pattList = null; // patterns (Patt) in pattMap
    Collection<Freq> freqList = null; // frequencies (Freq) in freqMap
    List<String> pattLines = new ArrayList<String>(); // to be written to the patterns output file
    List<String> freqLines = new ArrayList<String>(); // to be written to the frequencies output file
    Collection<Integer> oldPos = null; // old positions of a value in the map (lines in the input file)
    Map<Integer,List<Integer>> values = new TreeMap<Integer,List<Integer>>(); // value, positions
    Map<Integer,Integer> orderID = new TreeMap<Integer,Integer>(); // timestamp[0], pattern ID
    List<String> timestamps = new ArrayList<String>(); // timestamps from the input file
    List<Integer> pattValues = new ArrayList<Integer>(); // values from the input file
    String[] tokens = new String[2];
    int value; // actually the key in the map
    int counter = 0; // count line numbers in the input file (positions of elements in the map)
    int pattID = 0; // pattern ID
    boolean found; // pattern found
    boolean newPatt; // new pattern
    String startTimestamp1 = null;
    String startTimestamp2 = null;
    String endTimestamp = null; // for a certain pattern
    String endTimestamp1 = null;
    String endTimestamp2 = null;
    List<String> endTimestamps = null; // for a certain pattern
    int index = -1; // index in List<String> timestamps of endTimestamp
    while ((line=br.readLine())!=null)
    {
      tokens = line.split(","); // tokens[0] - time, tokens[1] - value
      timestamps.add(tokens[0]); // timestamps corresponding to positions of values in the map
      value = Integer.parseInt(tokens[1]); // and to lines in the input file
      pattValues.add(value);
      if (!values.containsKey(value))
      {
        List<Integer> pos = new ArrayList<Integer>(); // pos - positions of value in the map
        pos.add(counter);                         // (corresponding to line numbers in the input file)
        values.put(value,pos); // autoboxing - automatically turn int to Integer (value)
      }
      else
      {
        oldPos = values.get(value);
        if (counter>=(patternLength*2)) // it makes sense to search for repeating patterns
        {
          for (int oldCounter : oldPos) // autoboxing - automatically turn Integer to int
            if ((oldCounter>=patternLength)&&((counter-oldCounter)>=patternLength))
            {
              int[] tmpValues = new int[patternLength];
              startTimestamp1 = timestamps.get(oldCounter-patternLength+1);
              startTimestamp2 = timestamps.get(counter-patternLength+1);
              endTimestamp1 = timestamps.get(oldCounter);
              endTimestamp2 = timestamps.get(counter);
              found = true;
              for (int i=counter,j=oldCounter,k=patternLength; k>0; i--,j--,k--)
              {
                if ((int)pattValues.get(j)!=(int)pattValues.get(i))
                {
                  found = false;
                  break;
                }
                tmpValues[k-1] = pattValues.get(j); // pattValues.get(i)
              }
              if (found) // found pattern
              {
                StringBuilder str = new StringBuilder();
                for (int i=0; i<(patternLength-1); i++)
                {
                  str.append(tmpValues[i]);
                  str.append(' ');
                }
                str.append(tmpValues[patternLength-1]);
                String val = str.toString();
                newPatt = true;
                pattList = pattMap.values();
                freqList = freqMap.values();
                for (Patt p : pattList)
                {
                  if (val.equals(p.getValues())) // existing pattern - increment frequency
                  {
                    endTimestamps = p.getEndTimestamps();
                    endTimestamp = endTimestamps.get(endTimestamps.size()-1);
                    index = timestamps.indexOf(endTimestamp);
                    if ((counter-patternLength+1)>index) // start timestamp after previous end timestamp
                    {
                      p.addStartTimestamp(startTimestamp2);
                      p.addEndTimestamp(endTimestamp2);
                      int id = p.getID();
                      for (Freq f : freqList)
                        if (id==f.getPattID())
                        {
                          f.incrementFreq();
                          f.setEndTime(endTimestamp2);
                          break;
                        }
                    }
                    newPatt = false;
                    break;
                  }
                }
                if (newPatt) // new pattern
                {
                  pattID++; // first pattern has ID 1
                  Patt patt = new Patt(pattID,patternLength);
                  patt.setValues(val);
                  patt.addStartTimestamp(startTimestamp1);
                  patt.addEndTimestamp(endTimestamp1);
                  patt.addStartTimestamp(startTimestamp2);
                  patt.addEndTimestamp(endTimestamp2);
                  Freq freq = new Freq(pattID);
                  freq.setStartTime(startTimestamp1);
                  freq.setEndTime(endTimestamp2);
                  freq.setFreq(2);
                  pattMap.put(oldCounter-patternLength+1,patt);
                  freqMap.put(oldCounter-patternLength+1,freq);
                }
              }
            }
        }
        oldPos.add(counter); // add new position for value
      }
      counter++;
    }
    br.close();
    fr.close();
    pattList = pattMap.values();
    freqList = freqMap.values();
    pattID = 0;
    for (Patt p : pattList) // change pattern IDs to reflect the order they appear in the input file
      p.setID(++pattID);
    pattID = 0;
    for (Freq f : freqList) // change pattern IDs to reflect the order they appear in the input file
      f.setPattID(++pattID);
    writeFile(pattList,"patterns.txt");
    writeFile(freqList,"frequencies.txt");
//    System.out.println(values); // for testing - lines in the input file start with 1, in the map with 0
//    System.out.println(pattMap); // for testing
//    System.out.println(freqMap); // for testing
  }
  /**
   * @param coll Collection of objects to be written to the output file - using the method toString()
   * @param filename Output file name
   */
  static void writeFile(Collection<? extends Object> coll, String filename) throws IOException
  {
    File outputFile = new File(filename);
    FileWriter fwr = new FileWriter(outputFile);
    BufferedWriter bwr = new BufferedWriter(fwr);
    for (Object obj : coll) // for each Object obj in coll
      bwr.write(obj.toString()+'\n');
    bwr.close();
    fwr.close();
  }
}
/*
javadoc -d help -author -version -private -sourcepath . \
-link http://java.sun.com/javase/6/docs/api/ Patterns.java
// -d -- destination folder
// -author -- include information about the author
// -version -- include information about the version
// -private -- include all the fields and methods (private, protected, package, public)
// -sourcepath -- path to source (.java) files
// -link <address> -- create links for classes and methods found at <address>
// (retrieved from <address>/package-list)

javac Patterns.java
java Patterns
Usage: java Patterns inputFile [patternLength [distance]]

java Patterns archive.csv 0
Pattern length must be at least 2

java Patterns archive.csv

java Patterns archive.csv 4

java Patterns archive.csv 7

*/
